﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Numeric;

namespace KaroEngine
{
    public class TranspositionTable
    {
        #region Properties
        static TranspositionTable instance;

        List<List<List<int>>> randomTable;
        List<TranspositionTableElement> table;

        Random randomGenerator = new Random();
        int size = 750000;

        public int elementsCounter = 0;
        public int usagesCounter = 0;
        public int collisionsCounter = 0;
        #endregion

        #region Singleton Implementation
        public static TranspositionTable Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new TranspositionTable();
                }
                return instance;
            }
        }
        #endregion

        #region Constructors
        TranspositionTable()
        {
            table = new List<TranspositionTableElement>();

            for (int iterator = 0; iterator < size; iterator++)
                table.Add(new TranspositionTableElement());

            CreateRandomIndices();
        }
        #endregion

        #region Random hash indices generator
        /// <summary>
        /// Creates and fills in the table of the unique random indices
        /// </summary>
        void CreateRandomIndices()
        {
            randomTable = new List<List<List<int>>>();

            // for both players: Red, White
            for (short currentPlayerIterator = 0; currentPlayerIterator < 2; currentPlayerIterator++)
            {
                randomTable.Add(new List<List<int>>());
                randomTable[currentPlayerIterator] = new List<List<int>>();

                // for each tile state: Empty, WhiteNormal, WhiteUpsideDown, RedNormal, RedUpsideDown
                for (short tileStateIterator = 0; tileStateIterator < 5; tileStateIterator++)
                {
                    randomTable[currentPlayerIterator].Add(new List<int>());
                    randomTable[currentPlayerIterator][tileStateIterator] = new List<int>();

                    // for each position at the 21 x 20 table
                    for (short positionsIterator = 0; positionsIterator < 20 * 21; positionsIterator++)
                    {
                        bool IsNextTryNeeded = true;
                        int randomInteger;

                        // each random integer has to be unique
                        while (IsNextTryNeeded)
                        {
                            IsNextTryNeeded = false;
                            randomInteger = randomGenerator.Next(Int32.MaxValue);

                            // checking backwards if the new random number is unique indeed
                            for (short cpi = 0; (cpi < currentPlayerIterator - 1) && (IsNextTryNeeded == false); cpi++)
                                for (short tsi = 0; (tsi < tileStateIterator - 1) && (IsNextTryNeeded == false); tsi++)
                                    for (short psi = 0; (psi < positionsIterator - 1) && (IsNextTryNeeded == false); psi++)
                                        if (randomInteger == randomTable[currentPlayerIterator][tileStateIterator][positionsIterator])
                                            IsNextTryNeeded = true;
                        }
                        // saving unique random number to the table
                        randomTable[currentPlayerIterator][tileStateIterator].Add(randomGenerator.Next(Int32.MaxValue));
                    }
                }
            }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Calculates the zobrist hash value for the given board situation.
        /// </summary>
        public int GetZobristHash(Board boardSituation)
        {
            int hashValue = 0;

            for (short i = 0; i < boardSituation.Tiles.Count; i++)
                // only if there is a tile
                if (boardSituation.Tiles[i] != null)
                    hashValue ^= randomTable[(short)(boardSituation.CurrentPlayer)][(short)(boardSituation.Tiles[i].State)][i];
            return hashValue;
        }

        /// <summary>
        /// Calculates the zobrist hash value after the given move.
        /// </summary>
        public int GetZobristHash(Board boardSituation, Move move)
        {
            int hashPositionFrom = 0, hashPositionTo = 0, hashTileToMove = 0;
           
            if (move.PositionFrom != -1)
            {
                hashPositionFrom = randomTable[(short)boardSituation.CurrentPlayer][(short)boardSituation.Tiles[move.PositionFrom].State][move.PositionFrom];
            }

            if (move.PositionTo != -1)
            {
                if (move.PositionFrom == -1)
                {
                    // putting new Red cone to the board
                    if (boardSituation.CurrentPlayer == Player.Red)
                    {
                        hashPositionTo = randomTable[(short)Player.Red][(short)TileState.RedNormal][move.PositionTo];
                    }

                    // putting new White cone to the board
                    else
                    {
                        hashPositionTo = randomTable[(short)Player.White][(short)TileState.WhiteNormal][move.PositionTo];
                    }
                }

                else
                {
                    // checking whether it is a regular move or jump
                    if (move.PositionTo == move.PositionFrom + (short)Direction.InnerBottom ||
                        move.PositionTo == move.PositionFrom + (short)Direction.InnerBottomLeft ||
                        move.PositionTo == move.PositionFrom + (short)Direction.InnerBottomRight ||
                        move.PositionTo == move.PositionFrom + (short)Direction.InnerLeft ||
                        move.PositionTo == move.PositionFrom + (short)Direction.InnerRight ||
                        move.PositionTo == move.PositionFrom + (short)Direction.InnerTop ||
                        move.PositionTo == move.PositionFrom + (short)Direction.InnerTopLeft ||
                        move.PositionTo == move.PositionFrom + (short)Direction.InnerTopRight)
                    {
                        hashPositionTo = randomTable[(short)boardSituation.CurrentPlayer][(short)boardSituation.Tiles[move.PositionFrom].State][move.PositionTo];
                    }

                    else
                    {
                        hashPositionTo =
                                randomTable[(short) boardSituation.CurrentPlayer][
                                        (short) boardSituation.FlipCone( boardSituation.Tiles[move.PositionFrom].State )][
                                        move.PositionTo];
                    }
                }
            }

            if (move.TilePositionToMove != -1)
            {
                // removing the tile from the given position
                hashTileToMove = randomTable[(short)boardSituation.CurrentPlayer][(short)TileState.Empty][move.TilePositionToMove];
            }

            return boardSituation.ZobristHash ^ hashPositionFrom ^ hashPositionTo ^ hashTileToMove;
        }

        /// <summary>
        /// Checks if the given board situation is stored in the transposition table
        /// </summary>
        public bool IsInTranspositionTable(Board boardSituation, Move move)
        {
            int hash = GetZobristHash(boardSituation, move);

            if (table[hash % size].zobristHash == hash)
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// Stores the given evaluation value at the given position
        /// </summary>
        public void StoreInTranspositionTable(int zobristHash, short evaluation, short depth)
        {
            TranspositionTableElement t = new TranspositionTableElement();
            t.zobristHash = zobristHash;
            t.evaluation = evaluation;
            t.depth = depth;

            if (table[zobristHash % size] == new TranspositionTableElement())
            {
                table[zobristHash % size] = t;
            }
            else
            {
                collisionsCounter++;

                if (t.evaluation > table[zobristHash % size].evaluation)
                {
                    if ( t.depth > table[zobristHash % size].depth )
                    {
                        table[zobristHash % size] = t;
                    }
                }
            }

            elementsCounter++;
        }

        /// <summary>
        /// Returns the evaluation value stored at the given hash index
        /// </summary>
        public short ReadFromTranspositionTable(int zobristHash)
        {
            usagesCounter++;

            return table[zobristHash % size].evaluation;
        }
        #endregion
    }
}
